# API 接口文档

API 同时支持：

- 兼容模式（legacy）：`GET` + `MD5` 签名
- 推荐模式（v2）：`POST` + `HMAC-SHA256` + `时间戳/随机数` 防重放
- **生产环境域名**: `https://api.yourdomain.com`[Whisper - 现代卡密验证与授权系统](https://commu.fun/)`/api` (示例)
- **本地测试域名**: `https://commu.fun/api`

> **注意**: 为了方便演示，本文档以下示例均使用 `https://commu.fun/api` 作为基础 URL。请在实际对接时替换为您部署的实际域名。

- **通用返回格式**:

| 字段名             | 说明                    |
| :-------------- | :-------------------- |
| code            | 错误码，请求成功为 0           |
| msg             | 返回的消息内容，一般为错误信息       |
| data            | 接口返回数据                |
| encrypted\_data | (可选) 加密后的数据，如果开启了加密模式 |

- 如果返回数据为加密数据：
  - `enc_ver=2`：使用 AES-GCM 解密 `encrypted_data`
  - 未携带 `enc_ver`：视为 legacy AES-CBC（仅用于兼容旧客户端）

## Lua 脚本对接教程

系统支持 Lua 脚本分发（普通模式/安全模式）。完整对接教程提供下载：

- [Lua 脚本对接教程（下载）](/docs/download/lua_integration_guide.txt)
- 入口概览：
  - 普通模式：`POST /api/v1/activate`
  - 安全模式：`GET /api/v2/secure/public_key`、`POST /api/v2/secure/verify`、`GET /api/v2/secure/download`

***

## 验证卡密 / 激活

<span class="badge bg-blue text-white">POST</span> `/api/v2/check`（推荐）

<span class="badge bg-green text-white">GET</span> `/check`

这个API 用于验证卡密状态、激活新卡密以及绑定机器码。

### 请求参数

| 参数名              | 说明                            |
| :--------------- | :---------------------------- |
| **key**          | 卡密字符串 (例如: `KEY-XXXX`)        |
| **hwid**         | 机器码 (设备唯一标识)                  |
| **instance\_id** | 软件实例ID (Software Instance ID) |
| **sign**         | 签名 (算法见下文)                    |

v2 版本改为 JSON Body：

```json
{"key":"KEY-XXXX","hwid":"HWID-...","instance_id":"12345678"}
```

并携带请求头：

- `X-Timestamp`: 秒级时间戳
- `X-Nonce`: 随机字符串（一次性）
- `X-Signature`: `HMAC-SHA256` 签名

### 返回示例

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "status": "valid",
    "expire_date": "2026-12-31 23:59:59",
    "hwid": "HWID-ABC-123",
    "announcement": {
      "title": "系统公告",
      "content": "欢迎使用本系统！",
      "time": "2026-01-01 12:00:00"
    }
  }
}
```

### 公告下发（Announcement）

公告不需要单独调用接口获取：系统会在以下接口的成功响应中自动携带 `announcement` 字段（可能为 `null`）：

- `/api/v2/check`、`/check`
- `/api/v2/unbind`、`/unbind`
- `/api/v2/info`、`/info`
- `/cloudvar`
- `/log`

#### 后台如何配置公告

进入后台管理面板 → **公告管理**：

- **标题**：对应返回里的 `announcement.title`
- **内容**：对应返回里的 `announcement.content`
- **启用**：关闭后不会下发
- **所属实例**：选择后为“实例公告”；留空为“全局公告”（通常仅系统管理员需要）

#### 服务端选择规则（重要）

服务端会从公告表中取出最近一条启用公告作为下发内容：

- 过滤条件：`is_active = true` 且（`software_id = 当前实例ID` 或 `software_id is null`）
- 排序规则：按 `created_at` 倒序，取最新 1 条

也就是说：如果你后创建了一条全局公告，它会覆盖旧的实例公告（因为更“新”）。

***

## 心跳检测（卡密状态轮询）

<span class="badge bg-blue text-white">POST</span> `/api/v2/heartbeat`（推荐）

<span class="badge bg-green text-white">GET</span> `/heartbeat`

客户端激活卡密后，定期调用此接口检测卡密是否仍然有效（过期/封禁/机器码不匹配等）。

与 `/check` 不同，心跳接口**不会触发激活、不会绑定/换绑机器码、不写入事件日志**，适合高频轮询。

### 请求参数

| 参数名              | 说明                            |
| :--------------- | :---------------------------- |
| **key**          | 卡密字符串 (例如: `KEY-XXXX`)        |
| **hwid**         | 机器码 (设备唯一标识)                  |
| **instance\_id** | 软件实例ID (Software Instance ID) |
| **sign**         | 签名 (算法见下文)                    |

v2 版本改为 JSON Body：

```json
{"key":"KEY-XXXX","hwid":"HWID-...","instance_id":"12345678"}
```

并携带请求头：

- `X-Timestamp`: 秒级时间戳
- `X-Nonce`: 随机字符串（一次性）
- `X-Signature`: `HMAC-SHA256` 签名

### 返回示例

卡密有效：

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "status": "valid",
    "expire_date": "2026-12-31 23:59:59",
    "remaining_seconds": 86400,
    "announcement": null
  }
}
```

永久卡密：

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "status": "valid",
    "expire_date": "永久",
    "remaining_seconds": null,
    "announcement": null
  }
}
```

卡密已过期：

```json
{"code": 4, "msg": "卡密已过期"}
```

### 与 `/check` 的区别

| 特性       | `/check`（`/v2/check`） | `/heartbeat`（`/v2/heartbeat`） |
| :------- | :-------------------- | :--------------------------- |
| 激活未使用卡密  | ✅ 会激活                 | ❌ 返回 code=7                  |
| HWID 绑定  | ✅ 自动绑定/换绑             | ❌ 仅校验是否匹配                    |
| 事件日志     | ✅ 写入 EventLog         | ❌ 不写入                        |
| 更新 last\_seen | ✅                     | ✅                            |
| 返回剩余秒数   | ❌                     | ✅ remaining\_seconds          |
| 适用场景     | 首次激活 / 启动校验           | 运行中定期心跳                      |

### 错误码（心跳专用）

| code | 说明       |
| :--- | :------- |
| 7    | 卡密未激活    |

其余错误码与 `/check` 一致（见下方错误码说明）。

***

## 解绑卡密

<span class="badge bg-blue text-white">POST</span> `/api/v2/unbind`（推荐）

<span class="badge bg-green text-white">GET</span> `/unbind`

用于解绑卡密的机器码绑定。

### 请求参数

| 参数名              | 说明       |
| :--------------- | :------- |
| **key**          | 卡密字符串    |
| **hwid**         | 当前绑定的机器码 |
| **instance\_id** | 软件实例ID   |
| **sign**         | 签名       |

### 返回示例

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "status": "unbound_success"
  }
}
```

### 行为说明：自动解绑并重绑（限次数）

当卡密已激活且新设备上的 `hwid` 与已绑定的不同：

- 若设置了解绑次数上限 `unbind_limit`，且尚未用尽：
  - 系统自动将绑定迁移到新设备（当前 `hwid`）
  - 同时将 `unbind_count` 加 1
- 若已用尽次数：
  - 返回 `code=6`，`msg="当前卡密已绑定，无可换绑次数"`
  - 需要管理员在后台手动解绑

说明：

- 每张卡密仅允许同时绑定一个机器码；服务端使用行级锁保证并发下的唯一性。
- 如未设置 `unbind_limit` 或设置为负数，视为不限制；建议在后台为新卡密设置明确上限。

***

## 获取软件信息

<span class="badge bg-blue text-white">POST</span> `/api/v2/info`（推荐）

<span class="badge bg-green text-white">GET</span> `/info`

获取软件的最新版本号和更新内容。

### 请求参数

| 参数名              | 说明     |
| :--------------- | :----- |
| **instance\_id** | 软件实例ID |
| **sign**         | 签名     |

### 返回示例

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "version": "1.0.2",
    "update_content": "1. 修复了已知BUG\n2. 优化了性能"
  }
}
```

***

## 获取云变量

<span class="badge bg-green text-white">GET</span> `/cloudvar`

获取软件配置的云端变量（配置项）。

### 请求参数

| 参数名              | 说明        |
| :--------------- | :-------- |
| **key**          | 变量名 (Key) |
| **instance\_id** | 软件实例ID    |
| **sign**         | 签名        |

### 返回示例

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "value": "这里是云变量的值",
    "announcement": null
  }
}
```

***

## 发送事件日志

<span class="badge bg-green text-white">GET</span> `/log`

客户端上报自定义事件日志到后台。

### 请求参数

| 参数名              | 说明              |
| :--------------- | :-------------- |
| **message**      | 日志内容 (需 URL 编码) |
| **instance\_id** | 软件实例ID          |
| **sign**         | 签名              |

### 返回示例

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "status": "logged",
    "announcement": null
  }
}
```

***

## 卡密批量启停（后台管理）

用于后台“卡密管理”页面对选中卡密进行批量停用/启用。该接口依赖后台登录的 Session Cookie，不用于客户端 SDK 对接。

<span class="badge bg-blue text-white">POST</span> `/admin/card/command`

### 请求 Body（JSON）

停用卡密：

```json
{"action":"deactivate","keys":["KEY-1","KEY-2"]}
```

启用卡密：

```json
{"action":"activate","keys":["KEY-1","KEY-2"]}
```

### 返回格式（标准化）

```json
{
  "code": 0,
  "msg": "success",
  "data": {
    "action": "deactivate",
    "affected_count": 2,
    "requested_count": 2,
    "missing_count": 0,
    "results": [
      {"key":"KEY-1","ok":true,"before":"unused","after":"banned"},
      {"key":"KEY-2","ok":true,"before":"unused","after":"banned"}
    ]
  }
}
```

***

## 错误码说明

| code | 说明                            |
| :--- | :---------------------------- |
| 0    | success                       |
| 1    | 验证失败（或“卡密不存在”，当启用详细错误时）       |
| 2    | 卡密不属于该实例（启用详细错误时）             |
| 3    | 机器码不匹配（主动解绑接口）                |
| 4    | 卡密已过期                         |
| 5    | 卡密已封禁                         |
| 6    | 当前卡密已绑定，无可换绑次数（自动重绑失败，需要人工处理） |
| 7    | 卡密未激活（心跳接口专用）                    |

***

## 签名算法 (Signature)

### v2（推荐）：HMAC-SHA256 + 防重放

1. 取 Body 原始 JSON（建议紧凑格式 `separators=(',', ':')`）
2. 计算 `body_sha256 = sha256(body_bytes)`
3. 拼接签名串（换行分隔）：

`METHOD\nPATH\nTIMESTAMP\nNONCE\nBODY_SHA256`

其中 `PATH` 为请求路径（不含域名，不含 query），例如：`/api/v2/check`。

1. `signature = hmac_sha256(secret_key, message)`（十六进制小写）
2. 放入请求头：`X-Timestamp/X-Nonce/X-Signature`

### legacy（兼容）：MD5

所有 legacy 接口都需要 `sign` 参数，计算方法如下：

1. 将所有请求参数（除了 `sign` 本身）按照参数名的 **ASCII 码从小到大排序**。
2. 将排序后的参数拼接成字符串：`key1=value1&key2=value2...`
3. 在拼接好的字符串**末尾**直接加上后台设置的 `Secret Key`。
4. 对最终字符串进行 **MD5** 运算，结果即为签名。

**示例**:
假设参数为 `a=1`, `b=2`，密钥为 `secret`。

1. 排序并拼接: `a=1&b=2`
2. 加密钥: `a=1&b=2secret`
3. MD5: `md5("a=1&b=2secret")` -> `sign`

***

## 接入代码示例

***

## 模块下载（Python）

- 下载地址：/static/docs/whisper\_module.zip
- 内容包含：
  - 模块/whisper\_client.py（客户端）
  - 模块/__init__.py
  - 模块/whisper\_config.py（硬编码配置示例，发布前请替换为你的真实值）

### 使用步骤

1. 将 `whisper_config.py` 中的 `API_BASE_URL/INSTANCE_ID/SECRET_KEY/ENCRYPT_KEY` 替换为你的真实值
2. 解压后将 `模块` 文件夹置于你的项目根目录或包路径下
3. 安装依赖（如需解密）：
   - `pip install pycryptodome certifi`
4. 代码示例：

```python
from 模块 import get_default_client
client = get_default_client(verify_ssl=True)
res = client.check("KEY-XXXX...", "HWID-...")
print(res.ok, res.message, res.payload)
```

从响应中读取公告（若存在）：

```python
if res.ok:
    data = (res.payload or {}).get("data") or {}
    anno = data.get("announcement") or None
    if anno:
        print("公告标题:", anno.get("title"))
        print("公告内容:", anno.get("content"))
```

读取云变量与上报日志：

```python
v = client.cloudvar("some_key")
print(v.ok, v.payload)

r = client.log("client started")
print(r.ok, r.payload)
```

### 易语言 (EPL)

```e
.版本 2
.支持库 spec

.子程序 生成签名, 文本型
.参数 参数表, 文本型
.局部变量 待签文本, 文本型
.局部变量 通信密钥, 文本型

通信密钥 ＝ “YOUR_SECRET_KEY”
待签文本 ＝ 参数表 ＋ 通信密钥
返回 (取数据摘要 (到字节集 (待签文本)))
```

### Python

```python
import hashlib

def get_sign(params, secret_key):
    sorted_keys = sorted([k for k in params.keys() if k != 'sign'])
    param_str = "&".join([f"{k}={params[k]}" for k in sorted_keys])
    raw = param_str + secret_key
    return hashlib.md5(raw.encode('utf-8')).hexdigest()
```

***

## Lua 脚本分发接口

如果您使用了系统的 Lua 脚本分发功能，请参考独立文档：
[Lua脚本分发系统说明 (LUA\_SYSTEM\_README.md)](LUA_SYSTEM_README.md)
